home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / backend.py < prev    next >
Encoding:
Python Source  |  2009-03-18  |  19.9 KB  |  564 lines

  1. #
  2. # backend.py: interface for backends
  3. #
  4. # Copyright (C) 2008-2009 Canonical Ltd.
  5. #
  6. #    This program is free software: you can redistribute it and/or modify
  7. #    it under the terms of the GNU General Public License version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import os
  20. import re
  21. import stat
  22. from stat import *
  23. import sys
  24. import ufw.util
  25. from ufw.util import warn, debug
  26. from ufw.common import UFWError, config_dir, UFWRule
  27. import ufw.applications
  28.  
  29. class UFWBackend:
  30.     '''Interface for backends'''
  31.     def __init__(self, name, d, extra_files={}):
  32.         self.defaults = {}
  33.         self.name = name
  34.         self.dryrun = d
  35.         self.rules = []
  36.         self.rules6 = []
  37.  
  38.         self.files = {'defaults': os.path.join(config_dir, 'default/ufw'),
  39.                       'conf': os.path.join(config_dir, 'ufw/ufw.conf'),
  40.                       'apps': os.path.join(config_dir, 'ufw/applications.d') }
  41.         self.files.update(extra_files)
  42.  
  43.         self.loglevels = {'off':       0,
  44.                           'low':     100,
  45.                           'medium':  200,
  46.                           'high':    300,
  47.                           'full':    400 }
  48.  
  49.         self.do_checks = True
  50.         try:
  51.             self._do_checks()
  52.             self._get_defaults()
  53.             self._read_rules()
  54.         except Exception:
  55.             raise
  56.  
  57.         self.profiles = ufw.applications.get_profiles(self.files['apps'])
  58.         self.iptables_version = ufw.util.get_iptables_version()
  59.  
  60.     def _is_enabled(self):
  61.         if self.defaults.has_key('enabled') and \
  62.            self.defaults['enabled'] == 'yes':
  63.             return True
  64.         return False
  65.  
  66.     def use_ipv6(self):
  67.         if self.defaults.has_key('ipv6') and \
  68.            self.defaults['ipv6'] == 'yes' and \
  69.            os.path.exists("/proc/sys/net/ipv6"):
  70.             return True
  71.         return False
  72.  
  73.     def _do_checks(self):
  74.         '''Perform basic security checks:
  75.         is setuid or setgid (for non-Linux systems)
  76.         checks that script is owned by root
  77.         checks that every component in absolute path are owned by root
  78.         warn if script is group writable
  79.         warn if part of script path is group writable
  80.  
  81.         Doing this at the beginning causes a race condition with later
  82.         operations that don't do these checks.  However, if the user running
  83.         this script is root, then need to be root to exploit the race
  84.         condition (and you are hosed anyway...)
  85.         '''
  86.  
  87.         if not self.do_checks:
  88.             err_msg = _("Checks disabled")
  89.             warn(err_msg)
  90.             return True
  91.  
  92.         # Not needed on Linux, but who knows the places we will go...
  93.         if os.getuid() != os.geteuid():
  94.             err_msg = _("ERROR: this script should not be SUID")
  95.             raise UFWError(err_msg)
  96.         if os.getgid() != os.getegid():
  97.             err_msg = _("ERROR: this script should not be SGID")
  98.             raise UFWError(err_msg)
  99.         uid = os.getuid()
  100.  
  101.         if uid != 0:
  102.             err_msg = _("You need to be root to run this script")
  103.             raise UFWError(err_msg)
  104.  
  105.         # Use these so we only warn once
  106.         warned_world_write = {}
  107.         warned_group_write = {}
  108.         warned_owner = {}
  109.  
  110.         profiles = []
  111.         if not os.path.isdir(self.files['apps']):
  112.             warn_msg = _("'%s' does not exist") % (self.files['apps'])
  113.             warn(warn_msg)
  114.         else:
  115.             pat = re.compile(r'^\.')
  116.             for profile in os.listdir(self.files['apps']):
  117.                 if not pat.search(profile):
  118.                     profiles.append(os.path.join(self.files['apps'], profile))
  119.  
  120.         for path in self.files.values() + [ os.path.abspath(sys.argv[0]) ] + \
  121.                 profiles:
  122.             while True:
  123.                 debug("Checking " + path)
  124.                 if path == self.files['apps'] and \
  125.                            not os.path.isdir(self.files['apps']):
  126.                     break
  127.  
  128.                 try:
  129.                     statinfo = os.stat(path)
  130.                     mode = statinfo[ST_MODE]
  131.                 except OSError, e:
  132.                     err_msg = _("Couldn't stat '%s'") % (path)
  133.                     raise UFWError(err_msg)
  134.                 except Exception:
  135.                     raise
  136.  
  137.                 if statinfo.st_uid != 0 and not warned_owner.has_key(path):
  138.                     warn_msg = _("uid is %s but '%s' is owned by %s") % \
  139.                                 (str(uid), path, str(statinfo.st_uid))
  140.                     warn(warn_msg)
  141.                     warned_owner[path] = True
  142.                 if mode & S_IWOTH and not warned_world_write.has_key(path):
  143.                     warn_msg = _("%s is world writable!") % (path)
  144.                     warn(warn_msg)
  145.                     warned_world_write[path] = True
  146.                 if mode & S_IWGRP and not warned_group_write.has_key(path):
  147.                     warn_msg = _("%s is group writable!") % (path)
  148.                     warn(warn_msg)
  149.                     warned_group_write[path] = True
  150.  
  151.                 if path == "/":
  152.                     break
  153.  
  154.                 path = os.path.dirname(path)
  155.                 if not path:
  156.                     raise
  157.  
  158.         for f in self.files:
  159.             if f != 'apps' and not os.path.isfile(self.files[f]):
  160.                 err_msg = _("'%s' file '%s' does not exist") % (f, \
  161.                                                                 self.files[f])
  162.                 raise UFWError(err_msg)
  163.  
  164.     def _get_defaults(self):
  165.         '''Get all settings from defaults file'''
  166.         self.defaults = {}
  167.         for f in [self.files['defaults'], self.files['conf']]:
  168.             try:
  169.                 orig = ufw.util.open_file_read(f)
  170.             except Exception:
  171.                 err_msg = _("Couldn't open '%s' for reading") % (f)
  172.                 raise UFWError(err_msg)
  173.             pat = re.compile(r'^\w+="?\w+"?')
  174.             for line in orig:
  175.                 if pat.search(line):
  176.                     tmp = re.split(r'=', line.strip())
  177.                     self.defaults[tmp[0].lower()] = tmp[1].lower().strip('"\'')
  178.  
  179.             orig.close()
  180.  
  181.     def set_default(self, f, opt, value):
  182.         '''Sets option in defaults file'''
  183.         if not re.match(r'^[\w_]+$', opt):
  184.             err_msg = _("Invalid option")
  185.             raise UFWError(err_msg)
  186.  
  187.         try:
  188.             fns = ufw.util.open_files(f)
  189.         except Exception:
  190.             raise
  191.         fd = fns['tmp']
  192.  
  193.         found = False
  194.         pat = re.compile(r'^' + opt + '=')
  195.         for line in fns['orig']:
  196.             if pat.search(line):
  197.                 os.write(fd, opt + "=" + value + "\n")
  198.                 found = True
  199.             else:
  200.                 os.write(fd, line)
  201.  
  202.         # Add the entry if not found
  203.         if not found:
  204.             os.write(fd, opt + "=" + value + "\n")
  205.  
  206.         ufw.util.close_files(fns)
  207.  
  208.         # Now that the files are written out, update value in memory
  209.         self.defaults[opt.lower()] = value.lower().strip('"\'')
  210.  
  211.     def set_default_application_policy(self, policy):
  212.         '''Sets default application policy of firewall'''
  213.         if not self.dryrun:
  214.             if policy == "allow":
  215.                 self.set_default(self.files['defaults'], \
  216.                                             "DEFAULT_APPLICATION_POLICY", \
  217.                                             "\"ACCEPT\"")
  218.             elif policy == "deny":
  219.                 self.set_default(self.files['defaults'], \
  220.                                             "DEFAULT_APPLICATION_POLICY", \
  221.                                             "\"DROP\"")
  222.             elif policy == "reject":
  223.                 self.set_default(self.files['defaults'], \
  224.                                             "DEFAULT_APPLICATION_POLICY", \
  225.                                             "\"REJECT\"")
  226.             elif policy == "skip":
  227.                 self.set_default(self.files['defaults'], \
  228.                                             "DEFAULT_APPLICATION_POLICY", \
  229.                                             "\"SKIP\"")
  230.             else:
  231.                 err_msg = _("Unsupported policy '%s'") % (policy)
  232.                 raise UFWError(err_msg)
  233.  
  234.         rstr = _("Default application policy changed to '%s'") % (policy)
  235.  
  236.         return rstr
  237.  
  238.     def get_app_rules_from_template(self, template):
  239.         '''Return a list of UFWRules based on the template rule'''
  240.         rules = []
  241.         profile_names = self.profiles.keys()
  242.  
  243.         if template.dport in profile_names and template.sport in profile_names:
  244.             dports = ufw.applications.get_ports(self.profiles[template.dport])
  245.             sports = ufw.applications.get_ports(self.profiles[template.sport])
  246.             for i in dports:
  247.                 tmp = template.dup_rule()
  248.                 tmp.dapp = ""
  249.                 tmp.set_port("any", "src")
  250.                 try:
  251.                     (port, proto) = ufw.util.parse_port_proto(i)
  252.                     tmp.set_protocol(proto)
  253.                     tmp.set_port(port, "dst")
  254.                 except Exception:
  255.                     raise
  256.  
  257.                 tmp.dapp = template.dapp
  258.  
  259.                 if template.dport == template.sport:
  260.                     # Just use the same ports as dst for src when they are the
  261.                     # same to avoid duplicate rules
  262.                     tmp.sapp = ""
  263.                     try:
  264.                         (port, proto) = ufw.util.parse_port_proto(i)
  265.                         tmp.set_protocol(proto)
  266.                         tmp.set_port(port, "src")
  267.                     except Exception:
  268.                         raise
  269.  
  270.                     tmp.sapp = template.sapp
  271.                     rules.append(tmp)
  272.                 else:
  273.                     for j in sports:
  274.                         rule = tmp.dup_rule()
  275.                         rule.sapp = ""
  276.                         try:
  277.                             (port, proto) = ufw.util.parse_port_proto(j)
  278.                             rule.set_protocol(proto)
  279.                             rule.set_port(port, "src")
  280.                         except Exception:
  281.                             raise
  282.  
  283.                         if rule.protocol == "any":
  284.                             rule.set_protocol(tmp.protocol)
  285.  
  286.                         rule.sapp = template.sapp
  287.                         rules.append(rule)
  288.         elif template.sport in profile_names:
  289.             for p in ufw.applications.get_ports(self.profiles[template.sport]):
  290.                 rule = template.dup_rule()
  291.                 rule.sapp = ""
  292.                 try:
  293.                     (port, proto) = ufw.util.parse_port_proto(p)
  294.                     rule.set_protocol(proto)
  295.                     rule.set_port(port, "src")
  296.                 except Exception:
  297.                     raise
  298.  
  299.                 rule.sapp = template.sapp
  300.                 rules.append(rule)
  301.         elif template.dport in profile_names:
  302.             for p in ufw.applications.get_ports(self.profiles[template.dport]):
  303.                 rule = template.dup_rule()
  304.                 rule.dapp = ""
  305.                 try:
  306.                     (port, proto) = ufw.util.parse_port_proto(p)
  307.                     rule.set_protocol(proto)
  308.                     rule.set_port(port, "dst")
  309.                 except Exception:
  310.                     raise
  311.  
  312.                 rule.dapp = template.dapp
  313.                 rules.append(rule)
  314.  
  315.         if len(rules) < 1:
  316.             err_msg = _("No rules found for application profile")
  317.             raise UFWError(err_msg)
  318.  
  319.         return rules
  320.  
  321.     def update_app_rule(self, profile):
  322.         '''Update rule for profile in place. Returns result string and bool
  323.            on whether or not the profile is used in the current ruleset.
  324.         '''
  325.         updated_rules = []
  326.         updated_rules6 = []
  327.         last_tuple = ""
  328.         rstr = ""
  329.         updated_profile = False
  330.  
  331.         # Remember, self.rules is from user[6].rules, and not the running
  332.         # firewall.
  333.         for r in self.rules + self.rules6:
  334.             if r.dapp == profile or r.sapp == profile:
  335.                 # We assume that the rules are in app rule order. Specifically,
  336.                 # if app rule has multiple rules, they are one after the other.
  337.                 # If the rule ordering changes, the below will have to change.
  338.                 tuple = r.get_app_tuple()
  339.                 if tuple == last_tuple:
  340.                     # Skip the rule if seen this tuple already (ie, it is part
  341.                     # of a known tuple).
  342.                     continue
  343.                 else:
  344.                     # Have a new tuple, so find and insert new app rules here
  345.                     template = r.dup_rule()
  346.                     template.set_protocol("any")
  347.                     if template.dapp != "":
  348.                         template.set_port(template.dapp, "dst")
  349.                     if template.sapp != "":
  350.                         template.set_port(template.sapp, "src")
  351.                     try:
  352.                         new_app_rules = self.get_app_rules_from_template(\
  353.                                           template)
  354.                     except Exception:
  355.                         raise
  356.  
  357.                     for new_r in new_app_rules:
  358.                         new_r.normalize()
  359.                         if new_r.v6:
  360.                             updated_rules6.append(new_r)
  361.                         else:
  362.                             updated_rules.append(new_r)
  363.  
  364.                     last_tuple = tuple
  365.                     updated_profile = True
  366.             else:
  367.                 if r.v6:
  368.                     updated_rules6.append(r)
  369.                 else:
  370.                     updated_rules.append(r)
  371.  
  372.         if updated_profile:
  373.             self.rules = updated_rules
  374.             self.rules6 = updated_rules6
  375.             rstr += _("Rules updated for profile '%s'") % (profile)
  376.  
  377.             try:
  378.                 self._write_rules(False) # ipv4
  379.                 self._write_rules(True) # ipv6
  380.             except Exception:
  381.                 err_msg = _("Couldn't update application rules")
  382.                 raise UFWError(err_msg)
  383.  
  384.         return (rstr, updated_profile)
  385.  
  386.     def find_application_name(self, str):
  387.         '''Find the application profile name for str'''
  388.         if self.profiles.has_key(str):
  389.             return str
  390.  
  391.         match = ""
  392.         matches = 0
  393.         for n in self.profiles.keys():
  394.             if n.lower() == str.lower():
  395.                 match = n
  396.                 matches += 1
  397.  
  398.         debug_msg = "'%d' matches for '%s'" % (matches, str)
  399.         debug(debug_msg)
  400.         if matches == 1:
  401.             return match
  402.         elif matches > 1:
  403.             err_msg = _("Found multiple matches for '%s'. Please use exact profile name") % (str)
  404.         err_msg = _("Could not find a profile matching '%s'") % (str)
  405.         raise UFWError(err_msg)
  406.  
  407.     def find_other_position(self, position, v6):
  408.     '''Return the absolute position in the other list of the rule with the
  409.        user position of the given list. For example, find_other_position(4,
  410.        True) will return the absolute position of the rule in the ipv4 list
  411.            matching the user specified '4' rule in the ipv6 list.
  412.         '''
  413.         # Invalid search (v6 rule with too low position)
  414.         if v6 and position > len(self.rules6):
  415.             raise ValueError()
  416.  
  417.         # Invalid search (v4 rule with too high position)
  418.         if not v6 and position > len(self.rules):
  419.             raise ValueError()
  420.  
  421.         if position < 1:
  422.             raise ValueError()
  423.  
  424.         rules = []
  425.         if v6:
  426.             rules = self.rules6
  427.         else:
  428.             rules = self.rules
  429.  
  430.         # self.rules[6] is a list of tuples. Some application rules have
  431.         # multiple tuples but the user specifies by ufw rule, not application
  432.         # tuple, so we need to find how many tuples there are leading up to
  433.         # the specified position, which we can then use as an offset for
  434.         # getting the proper match_rule.
  435.         app_rules = {}
  436.         tuple_offset = 0
  437.         for i, r in enumerate(rules):
  438.             if i >= position:
  439.                 break
  440.             tuple = ""
  441.             if r.dapp != "" or r.sapp != "":
  442.                 tuple = r.get_app_tuple()
  443.  
  444.                 if app_rules.has_key(tuple):
  445.                     tuple_offset += 1
  446.                 else:
  447.                     app_rules[tuple] = True
  448.  
  449.         rules = []
  450.         if v6:
  451.             rules = self.rules
  452.             match_rule = self.rules6[position - 1 + tuple_offset].dup_rule()
  453.             match_rule.set_v6(False)
  454.         else:
  455.             rules = self.rules6
  456.             match_rule = self.rules[position - 1 + tuple_offset].dup_rule()
  457.             match_rule.set_v6(True)
  458.  
  459.         count = 1
  460.         for r in rules:
  461.             if UFWRule.match(r, match_rule) == 0:
  462.                 return count
  463.             count += 1
  464.  
  465.         return 0
  466.  
  467.     def get_loglevel(self):
  468.         '''Gets current log level of firewall'''
  469.         level = 0
  470.         rstr = _("Logging: ")
  471.         if not self.defaults.has_key('loglevel') or \
  472.            self.defaults['loglevel'] not in self.loglevels.keys():
  473.             level = -1
  474.             rstr += _("unknown")
  475.         else:
  476.             level = self.loglevels[self.defaults['loglevel']]
  477.             if level == 0:
  478.                 rstr += "off"
  479.             else:
  480.                 rstr += "on (%s)" % (self.defaults['loglevel'])
  481.         return (level, rstr)
  482.  
  483.     def set_loglevel(self, level):
  484.         '''Sets log level of firewall'''
  485.         if level not in self.loglevels.keys() + ['on']:
  486.             err_msg = _("Invalid log level '%s'") % (level)
  487.             raise UFWError(err_msg)
  488.  
  489.         new_level = level
  490.         if level == "on":
  491.            if not self.defaults.has_key('loglevel') or \
  492.               self.defaults['loglevel'] == "off":
  493.                new_level = "low"
  494.            else:
  495.                new_level = self.defaults['loglevel']
  496.  
  497.         self.set_default(self.files['conf'], "LOGLEVEL", new_level)
  498.         try:
  499.             self.update_logging(new_level)
  500.         except:
  501.             raise
  502.  
  503.         if new_level == "off":
  504.             return _("Logging disabled")
  505.         else:
  506.             return _("Logging enabled")
  507.  
  508.     def get_rules_count(self, v6):
  509.         '''Return number of ufw rules (not iptables rules)'''
  510.         rules = []
  511.         if v6:
  512.             rules = self.rules6
  513.         else:
  514.             rules = self.rules
  515.  
  516.         count = 0
  517.         app_rules = {}
  518.         for r in rules:
  519.             tuple = ""
  520.             if r.dapp != "" or r.sapp != "":
  521.                 tuple = r.get_app_tuple()
  522.  
  523.                 if app_rules.has_key(tuple):
  524.                     debug("Skipping found tuple '%s'" % (tuple))
  525.                     continue
  526.                 else:
  527.                     app_rules[tuple] = True
  528.             count += 1
  529.  
  530.         return count
  531.  
  532.     # API overrides
  533.     def get_default_policy(self):
  534.         raise UFWError("UFWBackend.get_default_policy: need to override")
  535.  
  536.     def set_default_policy(self, policy):
  537.         raise UFWError("UFWBackend.set_default_policy: need to override")
  538.  
  539.     def get_running_raw(self):
  540.         raise UFWError("UFWBackend.get_running_raw: need to override")
  541.  
  542.     def get_status(self, verbose, show_count):
  543.         raise UFWError("UFWBackend.get_status: need to override")
  544.  
  545.     def get_status_as_list(self):
  546.         raise UFWError("UFWBackend.get_status_as_list: need to override")
  547.  
  548.     def set_rule(self, rule, allow_reload):
  549.         raise UFWError("UFWBackend.set_rule: need to override")
  550.  
  551.     def start_firewall(self):
  552.         raise UFWError("UFWBackend.start_firewall: need to override")
  553.  
  554.     def stop_firewall(self):
  555.         raise UFWError("UFWBackend.stop_firewall: need to override")
  556.  
  557.     def get_app_rules_from_system(self, template, v6):
  558.         raise UFWError("UFWBackend.get_app_rules_from_system: need to " + \
  559.                        "override")
  560.  
  561.     def update_logging(self, level):
  562.         raise UFWError("UFWBackend.update_logging: need to override")
  563.  
  564.